appwrite auth workflow
Complete Authentication Workflow Documentation
Overview
This React blog application uses Appwrite as the Backend-as-a-Service (BaaS) for authentication, combined with Redux Toolkit for client-side state management. The authentication system provides user registration, login, logout, session persistence, and comprehensive route protection across the entire application.
Architecture Components
1. Authentication Service Layer (appwrite/auth.js)
The AuthService class acts as the main interface between the application and Appwrite's authentication API.
Class Structure:
export class AuthService {
client = new Client();
account;
constructor() {
this.client
.setEndpoint(conf.appwriteUrl)
.setProject(conf.appwriteProjectId);
this.account = new Account(this.client);
}
}
Key Methods:
-
createAccount({email, password, name})- Creates new user account and automatically logs them in -
login({email, password})- Authenticates user and creates session viacreateEmailPasswordSession() -
getCurrentUser()- Retrieves current authenticated user data viaaccount.get() -
logout()- Terminates all user sessions viaaccount.deleteSessions()
Session Management:
-
Uses Appwrite's built-in session management via
createEmailPasswordSession() -
Sessions are stored and managed by Appwrite SDK (likely in browser storage)
-
deleteSessions()clears all active sessions on logout -
Error handling: Returns null on getCurrentUser errors, logs errors for logout
2. Redux State Management (src/store/authSlice.js & src/store/store.js)
Manages authentication state throughout the application using Redux Toolkit.
Store Configuration (src/store/store.js):
import {configureStore} from '@reduxjs/toolkit';
import authSlice from './authSlice';
const store = configureStore({
reducer: {
auth: authSlice
}
});
State Structure (src/store/authSlice.js):
const initialState = {
status: false, // true if authenticated, false if not
userData: null // user information from Appwrite
}
Actions:
-
login(state, action)- Sets status to true and storesaction.payload.userData -
logout(state)- Sets status to false and clears userData to null
3. Configuration (src/conf/conf.js)
Centralizes environment variables for Appwrite connection:
const conf = {
appwriteUrl: String(import.meta.env.VITE_APPWRITE_URL),
appwriteProjectId: String(import.meta.env.VITE_APPWRITE_PROJECT_ID),
appwriteDatabaseId: String(import.meta.env.VITE_APPWRITE_DATABASE_ID),
appwriteCollectionId: String(import.meta.env.VITE_APPWRITE_COLLECTION_ID),
appwriteBucketId: String(import.meta.env.VITE_APPWRITE_BUCKET_ID),
tinyMce: String(import.meta.env.VITE_TINYMCE_API_KEY)
}
4. Application Entry Point (src/main.jsx)
Sets up the complete routing structure with authentication protection using React Router and Redux Provider.
Complete Router Configuration:
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{ path: "/", element: <Home /> },
{
path: "/login",
element: (
<AuthLayout authentication={false}>
<Login />
</AuthLayout>
),
},
{
path: "/signup",
element: (
<AuthLayout authentication={false}>
<Signup />
</AuthLayout>
),
},
{
path: "/all-posts",
element: (
<AuthLayout authentication>
<AllPosts />
</AuthLayout>
),
},
{
path: "/add-post",
element: (
<AuthLayout authentication>
<AddPost />
</AuthLayout>
),
},
{
path: "/edit-post/:slug",
element: (
<AuthLayout authentication>
<EditPost />
</AuthLayout>
),
},
{ path: "/post/:slug", element: <Post /> },
],
},
])
Route Categories:
-
Public Routes:
/(Home),/post/:slug(Individual post view) -
Auth-Protected Routes:
/all-posts,/add-post,/edit-post/:slug -
Public-Only Routes:
/login,/signup(redirect to home if already authenticated)
5. Data Service Layer (appwrite/config.js)
The Service class handles all post-related operations and file management, working alongside the authentication system.
Class Structure:
export class Service {
client = new Client();
Databases;
bucket;
constructor() {
this.client
.setEndpoint(conf.appwriteUrl)
.setProject(conf.appwriteProjectId);
this.Databases = new Databases(this.client);
this.bucket = new Storage(this.client);
}
}
Post Management Methods:
-
createPost({title, slug, content, featuredimage, status, userId, authorName})- Creates new blog post -
updatePost(slug, {title, content, featuredimage, status, authorName})- Updates existing post -
deletePost(slug)- Deletes post and returns boolean status -
getPost(slug)- Retrieves single post by slug -
getPosts(queries = [Query.equal('status',"active")])- Retrieves filtered posts
File Management Methods:
-
uploadFile(file)- Handles file uploads for featured images usingID.unique() -
deleteFile(fileId)- Removes uploaded files from storage -
getFileView(fileId)- Gets file preview URL (synchronous method)
Authentication Flow
Application Initialization (App.jsx)
-
App component mounts and triggers
useEffect -
Session check via
authService.getCurrentUser() -
State update based on session status:
- If user data exists → dispatch login({userData})
- If no user data → dispatch logout()
- Loading state managed until authentication check completes
// App.jsx
useEffect(() => {
authService.getCurrentUser()
.then((userData)=>{
if(userData){
dispatch(login({userData}))
}else{
dispatch(logout())
}
})
.finally(() => {
setLoading(false)
})
}, [])
User Registration Flow (src/components/Signup.jsx)
-
User submits registration form with name, email, and password
-
Form validation occurs using react-hook-form
-
authService.createAccount(data)called with validated data -
Appwrite creates account with
account.create(ID.unique(), email, password, name) -
Automatic login -
createAccountinternally callslogin()if account creation succeeds -
User data retrieval via
authService.getCurrentUser() -
Redux state update via
dispatch(login(userData)) -
Navigation to home page (
"/")
// Signup.jsx
const create = async(data) => {
setError("")
try {
const userData = await authService.createAccount(data)
if (userData) {
const userData = await authService.getCurrentUser()
if(userData) dispatch(login(userData));
navigate("/")
}
} catch (error) {
setError(error.message)
}
}
User Login Flow (src/components/Login.jsx)
-
User submits login form with email and password
-
Form validation occurs using react-hook-form
-
authService.login(data)creates session with Appwrite usingcreateEmailPasswordSession() -
User data retrieval via
authService.getCurrentUser() -
Redux state update via
dispatch(authLogin(userData)) -
Navigation to home page (
"/")
// Login.jsx
const login = async(data) => {
setError("")
try {
const session = await authService.login(data)
if (session) {
const userData = await authService.getCurrentUser()
if(userData) dispatch(authLogin(userData));
navigate("/")
}
} catch (error) {
setError(error.message)
}
}
User Logout Flow (src/components/Header/LogoutBtn.jsx)
-
User clicks logout button
-
authService.logout()terminates Appwrite sessions usingaccount.deleteSessions() -
Redux state update via
dispatch(logout()) -
Automatic navigation handled by AuthLayout component based on updated auth state
// LogoutBtn.jsx
const logoutHandler = () => {
authService.logout().then(() => {
dispatch(logout())
})
}
Route Protection System
AuthLayout Component (src/components/AuthLayout.jsx)
Acts as a Higher-Order Component (HOC) for route protection.
Parameters:
-
children- Components to render if authorized -
authentication- Boolean indicating if route requires authentication (true/false)
Protection Logic:
// AuthLayout.jsx
useEffect(() => {
if(authentication && authStatus !== authentication){
navigate("/login") // Redirect to login if auth required but user not authenticated
} else if(!authentication && authStatus !== authentication){
navigate("/") // Redirect to home if no auth required but user is authenticated
}
setLoader(false)
}, [authStatus, navigate, authentication])
Usage Pattern:
-
Wrap protected routes with
<AuthLayout authentication={true}> -
Wrap public-only routes (login/signup) with
<AuthLayout authentication={false}> -
Loading state shows "Loading..." while checking authentication
Complete Authentication State Access Points
Components Using Auth State:
- Header Component (
src/components/Header/Header.jsx)
- Accesses: useSelector(state => state.auth.status)
- Purpose: Conditionally renders navigation items and logout button
- Logic:
- Shows Login/Signup buttons when !authStatus
- Shows All Posts/Add Post buttons when authStatus is true
- Displays LogoutBtn component when authenticated
- Navigation Items:
```javascript
const navItems = [
{ name: 'Home', slug: "/", active: true },
{ name: "Login", slug: "/login", active: !authStatus },
{ name: "Signup", slug: "/signup", active: !authStatus },
{ name: "All Posts", slug: "/all-posts", active: authStatus },
]
```
- AuthLayout Component (
src/components/AuthLayout.jsx)
- Accesses: useSelector(state => state.auth.status)
- Purpose: Route protection and navigation control
- Loading State: Shows "Loading..." while checking auth status
- Conditional Rendering: Only renders children when loader is false
- Post Component (
src/pages/Post.jsx)
- Accesses: useSelector(state => state.auth.userData)
- Purpose: Determines if current user is the author of a post
- Author Check: const isAuthor = post && userData ? post.userId === userData.$id : false
- Conditional Rendering: Shows Edit/Delete buttons only to post authors
- Actions Available to Authors:
- Edit button → navigates to /edit-post/${post.$id}
- Delete button → calls appwriteService.deletePost() and appwriteService.deleteFile()
- PostForm Component (
src/components/post-form/PostForm.jsx)
- Accesses: useSelector(state => state.auth.userData)
- Purpose: Associates posts with the authenticated user during creation/editing
- User Data Usage:
- userId: userData.$id - Links post to user
- authorName: userData.name - Displays author name
- Security Check: Redirects to login if userData is not available
- Form Handling: Uses react-hook-form for form management
- LogoutBtn Component (
src/components/Header/LogoutBtn.jsx)
- No Direct Auth Access: But dispatches logout action
- Functionality:
- Calls authService.logout()
- Dispatches logout() action to Redux store
Page-Level Authentication Integration
Protected Pages (Require Authentication):
- AddPost Page (
src/pages/AddPost.jsx)
- Route: /add-post
- Protection: Wrapped with <AuthLayout authentication>
- Component: Renders PostForm component for creating new posts
- AllPost Page (
src/pages/AllPost.jsx)
- Route: /all-posts
- Protection: Wrapped with <AuthLayout authentication>
- Functionality: Displays all active posts using appwriteService.getPosts([])
- Layout: Grid layout with PostCard components
- EditPost Page (
src/pages/EditPost.jsx)
- Route: /edit-post/:slug
- Protection: Wrapped with <AuthLayout authentication>
- Functionality:
- Fetches existing post data via appwriteService.getPost(slug)
- Renders PostForm with pre-populated data
- Redirects to home if post not found
Public-Only Pages (Redirect if Authenticated):
- Login Page (
src/pages/Login.jsx)
- Route: /login
- Protection: Wrapped with <AuthLayout authentication={false}>
- Component: Wrapper for Login component from components directory
- Signup Page (
src/pages/Signup.jsx)
- Route: /signup
- Protection: Wrapped with <AuthLayout authentication={false}>
- Component: Wrapper for Signup component from components directory
Public Pages (No Authentication Required):
- Home Page (
src/pages/Home.jsx)
- Route: /
- No Protection: Accessible to all users
- Dynamic Content: Shows posts if available, otherwise shows landing page
- Post Page (
src/pages/Post.jsx)
- Route: /post/:slug
- No Protection: Anyone can view individual posts
- Author Features: Edit/Delete buttons only visible to post authors
Session Persistence and Redux State Management
How Redux Manages Auth State
- State Structure:
- status (boolean): Indicates if user is authenticated
- userData (object): Contains user information from Appwrite
- Action Creators:
- login(userData): Sets status to true and stores user data
- logout(): Sets status to false and clears user data
- State Access:
- Components access auth state via useSelector(state => state.auth.status)
- User data accessed via useSelector(state => state.auth.userData)
Session Persistence Flow
- Initial App Load:
- App component mounts and checks for existing session
- authService.getCurrentUser() queries Appwrite for active session
- If session exists, Redux store is updated with user data
- UI renders based on authentication status
- Session Storage:
- Appwrite SDK handles session storage internally (cookies/localStorage)
- No manual session token management needed in the application
- Sessions persist across browser refreshes
- Session Validation:
- AuthLayout component checks Redux auth state on route changes
- Protected routes redirect to login if no valid session
- Public-only routes redirect to home if session exists
- Session Termination:
- LogoutBtn component calls authService.logout()
- Appwrite SDK clears all sessions via deleteSessions()
- Redux store updated to reflect logged-out state
- UI updates based on new auth state
Error Handling
Authentication Errors:
-
Login/Signup forms catch and display authentication errors
-
getCurrentUser logs errors but doesn't throw (returns null)
-
Logout logs errors but continues execution
Network/Service Errors:
-
Errors are caught at component level and displayed to users
-
No global error handling middleware for auth failures
Security Considerations
Current Implementation:
-
Client-side state only - no server-side session validation
-
Appwrite sessions provide the security layer
-
Route protection via client-side redirects (can be bypassed)
Potential Improvements:
-
Server-side route protection
-
Token refresh handling
-
Session timeout management
-
More robust error handling
File Dependencies
Core Auth Files:
-
appwrite/auth.js- Authentication service -
src/store/authSlice.js- Redux state management -
src/components/AuthLayout.jsx- Route protection -
src/conf/conf.js- Configuration
Components Using Auth:
-
App.jsx- Session initialization -
src/components/Login.jsx- Login form -
src/components/Signup.jsx- Registration form -
src/components/Header/LogoutBtn.jsx- Logout functionality -
src/components/Header/Header.jsx- Navigation based on auth status -
src/pages/Post.jsx- Author verification -
src/components/post-form/PostForm.jsx- User association
Authentication Data Flow Diagram
┌─────────────────┐ ┌───────────────┐ ┌────────────────┐
│ │ │ │ │ │
│ User Actions │────▶│ Auth Service │────▶│ Appwrite API │
│ │ │ │ │ │
└─────────────────┘ └───────────────┘ └────────────────┘
│ │ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ │ │
└─────────────▶│ Redux Store │◀────────────┘
│ │
└───────────────┘
│
│
▼
┌─────────────────┐
│ │
│ React UI │
│ Components │
│ │
└─────────────────┘
Summary
This authentication system provides a complete user management solution with:
-
Centralized auth service via Appwrite
-
Global state management via Redux
-
Route protection via HOC pattern
-
Session persistence across browser sessions
-
Conditional UI rendering based on auth status
The system is well-structured, using modern React patterns and leveraging Appwrite's built-in authentication capabilities while maintaining a clean separation of concerns between authentication logic and UI components.